/*
 * Analyzer.java
 * 
 * Copyright©2012-2013 JMAS corporation. All rights reserved.
 */
package jp.co.jmas.jmasvideo.util;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import jp.co.jmas.jmasvideo.dao.ClassInfo;
import jp.co.jmas.jmasvideo.dao.Student;
import android.util.Log;

public class Analyzer {
	/**
	 * 音声入力から候補となるクラス名、組を取得する
	 * @param resultList
	 * @param classList
	 * @return
	 */
	public static List<Student> findCandidates(ArrayList<String> resultList, List<ClassInfo> classList) {
		final int maxCandidate = 4;

		for (String result : resultList) {
			Log.v("音声入力情報", result);
		}

		// 比較用文字列
		List<String> voiceList = new ArrayList<String>();
		for (String result : resultList) {
			String voice = removeSuffix(hiraganize(result));
			if (isHiragana(voice)) {
				voiceList.add(voice);
			}
		}

		// 園児一覧の作成
		List<Candidate<Student>> candidateList = new ArrayList<Candidate<Student>>();
		for (ClassInfo classInfo : classList) {
			for (Student student : classInfo.getStudentList()) {
				// 最短レーベンシュタイン距離を算出
				Candidate<Student> candidate = new Candidate<Student>(student);
				if (candidate.estimate(student.getKana(), voiceList)) {
					candidateList.add(candidate);
				}
			}
		}

		// 上位のみ抽出
		Collections.sort(candidateList);
		List<Student> studentList = new ArrayList<Student>();
		int size = Math.min(maxCandidate, candidateList.size());
		for (int i=0; i<size; i++) {
			studentList.add(candidateList.get(i).value);
		}
		return studentList;
	}

	/**
	 * ひらがな変換
	 * @param str
	 * @return
	 */
	private static String hiraganize(String str) {
		StringBuffer sb = new StringBuffer(str);

		// TODO 漢字→ひらがな変換
		String [][] kanjis = new String[][] {
			{"薔薇", "ばら"}, {"百合", "ゆり"}, {"組", "くみ"},
			{"君", "くん"}, {"子", "こ"}
		};
		for (String[] pair : kanjis) {
			int index = sb.indexOf(pair[0]);
			if (index >= 0) {
				sb.replace(index, index + pair[0].length(), pair[1]);
			}
		}
	
		for (int i = 0; i < sb.length(); i++) {
			char c = sb.charAt(i);
	
			// カタカナ→ひらがな変換
			if (c >= 'ァ' && c <= 'ン') {
				sb.setCharAt(i, (char)(c - 'ァ' + 'ぁ'));
			} else {
				switch (c) {
				case 'ヵ':
					sb.setCharAt(i, 'ゕ');
					break;
				case 'ヶ':
					sb.setCharAt(i, 'ゖ');
					break;
				case 'ヴ':
					sb.setCharAt(i, 'ゔ');
					i++;
					break;
				case 'ｬ':
					sb.setCharAt(i, 'ゃ');
					break;
				case 'ｭ':
					sb.setCharAt(i, 'ゅ');
					break;
				case 'ｮ':
					sb.setCharAt(i, 'ょ');
					break;
				default:
				}
			}
		}
		return sb.toString(); 
	}

	/**
	 * 敬称の削除
	 * @param str
	 */
	private static String removeSuffix(String str) {
		String [] suffixes = new String[] {"くん", "ちゃん", "さん"};

		for (String suffix : suffixes) {
			int index = str.lastIndexOf(suffix);
			if (index >=0 && str.length() == index + suffix.length()) {
				return str.substring(0, index);
			}
		}
		return str;
	}

	/**
	 * ひらがな文字列かどうかの判定
	 * @param str
	 * @return
	 */
	private static boolean isHiragana(String str) {
		return str.matches("^[\\u3040-\\u309F]+$");
	}

	/**
	 * 2文字間の編集距離(Levenshtein distance;レーベンシュタイン距離)の算出
	 * @param str1
	 * @param str2
	 * @return
	 */
	private static int getLevenshteinDistance(String str1, String str2) {
		final int costReplace = 1;
		final int costDelete  = 1;
		final int costInsert  = 1;
		int len1=str1.length();
		int len2=str2.length();
		int[][] row = new int[len1 + 1][len2 + 1];
		int i,j;
		int result;

		for(i=0; i<len1 + 1; i++) {
			row[i][0] = i;
		}
		for(i=0;i<len2 + 1; i++) {
			row[0][i] = i;
		}
		for(i=1; i<=len1; i++) {
			for(j=1;j<=len2; j++) {
				row[i][j] = Math.min(
					Math.min(
						row[i-1][j-1] + (str1.charAt(i-1) == str2.charAt(j-1) ? 0 : costReplace), // replace
						row[i][j-1]   + costDelete      // delete
					),
					row[i-1][j] + costInsert);  // insert
			}
		}
		result=row[len1][len2];

		return result;
	}

	private static class Candidate<T> implements Comparable<Candidate<T>> {
		/** レーベンシュタイン距離のしきい値 */
		private static final int THRESHOLD = 2;

		/** */
		private T value;

		/** レーベンシュタイン距離 */
		private int distance;

		public Candidate(T value) {
			this.value    = value;
			this.distance = THRESHOLD;
		}

		public boolean estimate(String value, List<String> voiceList) {
			// 文字列から最小のレーベンシュタイン距離を求める
			distance = THRESHOLD;
			for (String voice : voiceList) {
				distance = Math.min(
					distance,
					getLevenshteinDistance(value, voice));
			}
			return isCandidate();
		}

		public boolean isCandidate() {
			return (distance < THRESHOLD);
		}

		@Override
		public int compareTo(Candidate<T> another) {
			return (distance - another.distance); // 距離の短い順
		}
	}
}
